﻿namespace PugLib.IO
{
	using System;
	using System.Collections.Generic;
	using System.IO;
	using System.Threading;

	using Extensions.Collections;

	public static class Files
	{
		public static string CreateFileGuid()
		{
			return Guid.NewGuid().ToString().Replace("-", string.Empty);
		}

		public static List<string> GetFilesOfTypeFromDirectory(IEnumerable<string> filePatterns, string rootDirectory, SearchOption searchOption)
		{
			string[] tmpFiles;
			List<string> allFiles = new List<string>();

			filePatterns.ForEach(pattern =>
			{
				tmpFiles = Directory.GetFiles(rootDirectory, pattern, searchOption);
				if (tmpFiles.Length > 0)
				{
					allFiles.AddRange(tmpFiles);
				}
			});
			return allFiles;
		}

		public static List<string> GetFilesOfTypeFromDirectories(IEnumerable<string> filePatterns, IEnumerable<string> directories, SearchOption searchOption)
		{
			List<string> allFiles = new List<string>();
			directories.ForEach(directory => allFiles.AddRange(GetFilesOfTypeFromDirectory(filePatterns, directory, searchOption)));
			return allFiles;
		}

		public static List<string> GetFilesOfTypeFromDirectories(string filePattern, IEnumerable<string> directories, SearchOption searchOption)
		{
			List<string> allFiles = new List<string>();
			directories.ForEach(directory => allFiles.AddRange(GetFilesOfTypeFromDirectory(new[] {filePattern}, directory, searchOption)));
			return allFiles;
		}

		/// <summary>
		/// Spawns a background thread to delete the supplied file.  It sleeps/retries if it doesn't delete it the first time.
		/// It aborts if it can't delete the file within 1 minute or the current user doesn't have rights.
		/// </summary>
		/// <param name="fullPathToFile"></param>
		public static void BackgroundFileDelete(string fullPathToFile)
		{
			ThreadPool.QueueUserWorkItem(DeleteFile, fullPathToFile);
		}

		public static void BackgroundFileDelete(List<string> files)
		{
			if (files != null && files.Count > 0)
			{
				files.ForEach(BackgroundFileDelete);
			}
		}

		private static void DeleteFile(object path)
		{
			bool fileDeleted = false;
			string fullPathToFile = SafeConvert.ToString(path);
			DateTime timeout = DateTime.Now.AddMinutes(1);
			Thread.Sleep(1000);

			while ((false == fileDeleted) && (DateTime.Now < timeout))
			{
				try
				{
					if (File.Exists(fullPathToFile))
					{
						File.Delete(fullPathToFile);
					}
					fileDeleted = (false == File.Exists(fullPathToFile));
				}
				catch (UnauthorizedAccessException)
				{
					fileDeleted = true; // abort attempt                    
				}
				catch // lump all IO Exceptions and others in to same sleep case
				{
					// some other proc still has it, sleep and wait.  there's not really a better way of doing this
					Thread.Sleep(500);
				}
			}
		}

		/// <summary>
		/// Securely moves a file to a new location. Overwrites any
		/// preexisting file at new location (= replacing file).
		/// </summary>
		/// <remarks>
		/// If NTFS is available this is done via File.Replace.
		/// If NTFS is not available it will be moved via deleting
		/// any preexisting file and moving. Do NOT rely on the
		/// backupFile being there - or not - after the move process.
		/// That is not predetermined. This method is clearly
		/// optimized for the case that NTFS is available. Consider NOT
		/// using it on any other filesystem, if performance is an issue!
		/// </remarks>
		/// <param name="sourceLocation">The file to be  moved.</param>
		/// <param name="targetLocation">The new resting place of the file.</param>
		/// <param name="backupLocation">A backup location that is used when replacing on NTFS.</param>
		public static void FileMove(FileInfo sourceLocation, FileInfo targetLocation, FileInfo backupLocation)
		{
			if (targetLocation.Exists)
			{
				try
				{
					File.Replace(sourceLocation.FullName, targetLocation.FullName, backupLocation.FullName, true);
				}
				catch (PlatformNotSupportedException)
				{
					// Not operating on an NTFS volume
					if (targetLocation.Exists)
					{
						targetLocation.Delete();
					}
					File.Move(sourceLocation.FullName, targetLocation.FullName);
				}
			}
			else
			{
				File.Move(sourceLocation.FullName, targetLocation.FullName);
			}
		}

		/// <summary>
		/// Securely moves a file to a new location. Overwrites any
		/// preexisting file at new location (= replacing file).
		/// </summary>
		/// <remarks>
		/// If NTFS is available this is done via File.Replace.
		/// If NTFS is not available it will be moved via deleting
		/// any preexisting file and moving. Do NOT rely on the
		/// backupFile being there - or not - after the move process.
		/// That is not predetermined. This method is clearly
		/// optimized for the case that NTFS is available. Consider NOT
		/// using it on any other filesystem, if performance is an issue!
		/// </remarks>
		/// <param name="sourceLocation">The file to be  moved.</param>
		/// <param name="targetLocation">The new resting place of the file.</param>
		/// <param name="backupLocation">A backup location that is used when replacing on NTFS.</param>
		public static void FileMove(String sourceLocation, String targetLocation, String backupLocation)
		{
			if (File.Exists(targetLocation))
			{
				try
				{
					File.Replace(sourceLocation, targetLocation, backupLocation, true);
				}
				catch (PlatformNotSupportedException)
				{
					// Not operating on an NTFS volume
					if (File.Exists(targetLocation))
					{
						File.Delete(targetLocation);
					}
					File.Move(sourceLocation, targetLocation);
				}
			}
			else
			{
				File.Move(sourceLocation, targetLocation);
			}
		}
	}
}